// Copyright DustedPixels.com 2008. All rights reserved.

package com.dustedpixels.jasmin.unit.zx;

import com.dustedpixels.jasmin.unit.Unit;
import com.dustedpixels.jasmin.unit.math.Res32;
import com.dustedpixels.jasmin.unit.memory.RAM;
import com.dustedpixels.jasmin.unit.memory.ROM;
import com.dustedpixels.jasmin.unit.ula.ULA;
import com.dustedpixels.jasmin.unit.z80.v1.Z80;
import com.dustedpixels.jasmin.video.VideoOutput;

/**
 * ZX Spectrum+ Unit.
 * Real-time speed: 14MHz
 * 
 * @author micapolos@gmail.com (Michal Pociecha-Los)
 */
public final class ZXSpectrumPlus implements Unit {
  
  // 16KB ROM block, mapped from 0x0000-0x3FFF
  private final ROM rom;
  
  // 16KB video RAM, mapped from 0x4000-0x7FFF
  private final RAM vram;
  
  // 32KB extended RAM, mapped from 0x8000-0xFFFF
  private final RAM xram;
  
  // ULA chip
  private final ULA ula;
  
  // Zilog Z80 processor
  private final Z80 z80;
  
  // Resistor splitting data bus between ULA and Z80
  private final Res32 dataBusResistor;
  
  // Resistor splitting address bus between ULA and Z80
  private final Res32 addressBusResistor;

  public ZXSpectrumPlus(VideoOutput videoOutput, int[] vramData) {
    this.rom = new ROM(14, new int[1 << 14]);
    this.vram = new RAM(14, vramData);
    this.xram = new RAM(15, new int[1 << 15]);
    this.ula = new ULA(videoOutput);
    this.z80 = new Z80();    
    this.dataBusResistor = new Res32();
    this.addressBusResistor = new Res32();
  }
  
  public void updateEndPoints() {
    ula.updateEndPoints();
 
    z80.CLK = ula.CLOCK;
    z80.updateEndPoints();
    
    // Update buses on Z80 side
    addressBusResistor.A = z80.ADDR;
    addressBusResistor.A_Z = z80.ADDR_Z;
    
    dataBusResistor.A = (z80.DATA & z80.DATA_Z) | (rom.DATA & rom.DATA_Z) | (xram.DATA & xram.DATA_Z);
    dataBusResistor.A_Z = z80.DATA_Z | rom.DATA_Z | xram.DATA_Z;
    
    // Update buses on ULA side.
    addressBusResistor.B = ula.DADDR;
    addressBusResistor.B_Z = ula.DADDR_Z;
    
    dataBusResistor.B = (ula.DATA & ula.DATA_Z) | (vram.DATA & vram.DATA_Z);
    dataBusResistor.B_Z = ula.DATA_Z | vram.DATA_Z;
    
    // Resolve signal on both buses issues by resistors.
    addressBusResistor.updateEndPoints();
    dataBusResistor.updateEndPoints();
    
    // Propagate signal on buses back to devices on Z80 side.
    z80.ADDR = (z80.ADDR & z80.ADDR_Z) | (addressBusResistor.A & ~z80.ADDR_Z);
    z80.DATA = (z80.DATA & z80.DATA_Z) | (dataBusResistor.A & ~z80.DATA_Z);
    
    rom.ADDR = addressBusResistor.A;
    rom.DATA = (rom.DATA & rom.DATA_Z) | (dataBusResistor.A & ~rom.DATA_Z);
    
    xram.ADDR = addressBusResistor.A;
    xram.DATA = (xram.DATA & xram.DATA_Z) | (dataBusResistor.A & ~xram.DATA_Z);
    
    // Propagate signal on buses back to devices on ULA side.
    ula.DADDR = (ula.DADDR & ula.DADDR_Z) | (addressBusResistor.B & ~ula.DADDR_Z);
    ula.DATA = (ula.DATA & ula.DATA_Z) | (dataBusResistor.B & ~ula.DATA_Z);
    
    vram.ADDR = addressBusResistor.B;
    vram.DATA = (vram.DATA & vram.DATA_Z) | (dataBusResistor.B & ~vram.DATA_Z);

    // Update ROM control pins
    rom.RD = ula.ROMSEL;    
    rom.updateEndPoints();
    
    // Update Video RAM control pins
    vram.REQ = ula.DMREQ;
    vram.WR = ula.DWR;
    vram.updateEndPoints();

    // Update Extended RAM control pins
    xram.REQ = ((addressBusResistor.A & 0x8000) == 1);
    xram.WR = (z80.CTRL & com.dustedpixels.jasmin.unit.z80.v1.WR) != 0;
    xram.updateEndPoints();
  }
}
